package com.ziytek.web.citizen.service;

import com.google.gson.Gson;
import com.ziytek.web.citizen.caffeine.CachePutOrGet;
import com.ziytek.web.citizen.caffeine.CaffeinePut;
import com.ziytek.web.citizen.config.caffeine.CaffeineUtil;
import com.ziytek.web.citizen.constant.NumConstant;
import com.ziytek.web.citizen.constant.ServerConstant;
import com.ziytek.web.citizen.constant.SettingConstant;
import com.ziytek.web.citizen.event.CommandFirstDataEvent;
import com.ziytek.web.citizen.exception.IssuedException;
import com.ziytek.web.citizen.mapper.BasicCabinetIpMapper;
import com.ziytek.web.citizen.mapper.BoardConfigMapper;
import com.ziytek.web.citizen.mapper.OperationDetailMapper;
import com.ziytek.web.citizen.mapper.SettingDetailMapper;
import com.ziytek.web.citizen.model.bean.CommandIssuedSuccess;
import com.ziytek.web.citizen.model.bean.IssuedProtocol;
import com.ziytek.web.citizen.model.vo.OperationVo;
import com.ziytek.web.citizen.pojo.basic.OperationDetail;
import com.ziytek.web.citizen.service.itf.StepSettingsIssuedSingleService;
import com.ziytek.web.citizen.util.*;
import io.netty.buffer.Unpooled;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * @author fenghx
 */

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DeviceRequestService {
    private final Gson gson;
    private final CaffeinePut caffeinePut;
    private final DoorsService doorsService;
    private final CachePutOrGet cachePutOrGet;
    private final BoardConfigMapper boardConfigMapper;
    private final BasicCabinetIpMapper cabinetIpMapper;
    private final StepSettingsIssuedSingleService service;
    private final SettingDetailMapper settingDetailMapper;
    private final OperationDetailMapper operationDetailMapper;
    private final OperationVoCacheService operationVoCacheService;
    private final CommandBoardInfoService commandBoardInfoService;
    private final CommandFirstDataService commandFirstDataService;
    private final CommandOperationService commandOperationService;

    public void deviceRequestSingleChannel(String cabinetNo, List<Integer> boardNos) {
        Assert.notNull(cabinetNo, "deviceRequestSingleChannel 机柜号不能为空");
        Assert.notEmpty(boardNos, "deviceRequestSingleChannel boardNos 机柜号不能为空");
        log.info("设备断连重连业务请求,机柜号[{}]-单个恒流板业务重新下发", cabinetNo);

        var cab = String.valueOf(Integer.parseInt(cabinetNo));
        long nowTime = System.currentTimeMillis();
        for (var boardNo : boardNos) {
            var ops = operationDetailMapper.findDetailByCabinetNoAndBoardNoAndStartFlag(cab, boardNo, ServerConstant.START_FLAG_INTERRUPT, nowTime);
            if (CollectionUtils.isEmpty(ops)) {
                ops = operationDetailMapper.findDetailByCabinetNoAndBoardNo(cab, boardNo, nowTime);
            }
            if (CollectionUtils.isEmpty(ops)) {
                log.info("断连重新下发,未找到操作详情:{}-{}", cabinetNo, gson.toJson(boardNos));
                continue;
            }

            singleIssuedByList(ops, cab);
        }
    }

    public void deviceRequestAllChannel(String cabinetNo) {
        Assert.notNull(cabinetNo, "deviceRequestAllChannel 机柜号不能为空");
        log.info("设备断连重连业务请求,机柜号[{}]全部恒流板业务重新下发", cabinetNo);

        List<Integer> boardNoList = doorsService.getBoardNoList();
        if (CollectionUtils.isEmpty(boardNoList)) {
            log.info("断连重新下发,未找到恒流板配置:{}", cabinetNo);
            return;
        }

        boardNoList.sort(Integer::compareTo);
        log.info("设备断连重连业务请求,机柜号[{}]全部恒流板业务重新下发按单个通道执行下发", cabinetNo);
        deviceRequestSingleChannel(cabinetNo, boardNoList);

        var key = "commandAll&" + cabinetNo;
        CommandIssuedSuccess command = CaffeineUtil.get(key);
        if (null != command) {
            log.info("[全部通道]设备断连重连业务请求,机柜号[{}]设置工步定时任务:{}", cabinetNo, gson.toJson(command));
            var cacheKey = CaffeineUtil.getCacheKey(Integer.parseInt(command.getReqCabinetNo()), command.getBoardNo(), command.getChannelNo(), command.getOrder());
            CaffeineUtil.set(CaffeineUtil.CACHE_KEY_COMMAND_FIRST + cacheKey, System.currentTimeMillis(), 50);
            commandFirstData(command);
            CaffeineUtil.delete(key);
        }
    }

    private Long getTime(Long interruptTimeSecond, Integer expectedTime) {
        long i0 = interruptTimeSecond / NumConstant.LONG_TEN;
        //long y0 = interruptTimeSecond % NumConstant.LONG_TEN;
        //long t = y0 == NumConstant.INT_ZERO ? i0 : (i0 + NumConstant.INT_ONE);
        // 示例: 原先设置为 60  中断时为 20->10 19->
        interruptTimeSecond = i0 * NumConstant.LONG_TEN;

        long allTime;
        long time = expectedTime * NumConstant.LONG_SIXTY;
        long diff = time - interruptTimeSecond;
        long i = diff / NumConstant.LONG_TEN;
        long y = diff % NumConstant.LONG_TEN;
        // 计算剩余时长 秒
        allTime = y == NumConstant.INT_ZERO ? i * NumConstant.LONG_TEN : (i + NumConstant.INT_ONE) * NumConstant.LONG_TEN;
        return allTime < NumConstant.LONG_ZERO ? time : allTime;
    }

    private void singleIssuedByList(List<OperationDetail> ops, String cab) {
        if (CollectionUtils.isEmpty(ops) || StringUtils.isBlank(cab)) {
            return;
        }

        var cabinetNo = CabinetNoUtil.getCabinetNo(cab);
        Pair<List<String>, List<CommandIssuedSuccess>> pair = getPair(ops, cab);
        List<String> cmdList = pair.getLeft();
        List<CommandIssuedSuccess> lists = pair.getRight();

        // 下发指令为空
        if (CollectionUtils.isEmpty(cmdList) || CollectionUtils.isEmpty(lists)) {
            return;
        }

        // 拼接下发指令
        var sendMsg = getCommand(cabinetNo, cmdList);
        if (StringUtils.isBlank(sendMsg)) {
            throw new IssuedException("10006", "指令生成失败");
        }

        try {
            var ips = cabinetIpMapper.queryByCabinetNo(cabinetNo, SettingConstant.IS_AVAILABLE);
            if (ips == null) {
                throw new IssuedException("10004", "机柜IP不存在");
            }

            var ctx = caffeinePut.getChannelContext(ips.getIp());
            if (ctx == null) {
                throw new IssuedException("10005", "设备连接失败");
            }

            // 对接需要16进制
            ctx.writeAndFlush(Unpooled.buffer().writeBytes(HexUtil.hexString2Bytes(sendMsg)));

            log.info("中断请求重新下发,机柜[{}]-重新指令下发[{}]", cabinetNo, sendMsg);
        } catch (Exception e) {
            log.error("控制指令下发,机柜[{}]指令下发下发失败Charge and discharge control command issued ", cabinetNo, e);
            throw new IssuedException("10007", "指令下发异常");
        }

        // 删除缓存
        deleteCacheAddJobAndCache(lists);
    }

    private String getCommand(String cabinetNo, List<String> oneLoadDataList) {
        if (StringUtil.isNumeric(cabinetNo) && StringUtil.toInt(cabinetNo) <= NumConstant.INT_LT_ZERO) {
            throw new IssuedException("50001", "机柜地址异常");
        }

        var address = StringUtil.isNumeric(cabinetNo) ? Integer.toHexString(StringUtil.toInt(cabinetNo)) : cabinetNo;
        var loadData = new StringBuilder();
        for (var s : oneLoadDataList) {
            loadData.append(s);
        }

        return getString(address, loadData.toString());
    }

    protected String getString(String address, String loadData) {
        if (StringUtils.isAnyBlank(address, loadData)) {
            return null;
        }

        var issuedProtocol = new IssuedProtocol();
        // 待确认
        issuedProtocol.setDataType(ServerConstant.CMD_TYPE_CHARGE_DISCHARGE_CONTROL);
        issuedProtocol.setCabinetAddress(CabinetNoUtil.getCabinetNo(address));
        var data = loadData.trim().toUpperCase();

        issuedProtocol.setLoadData(data);
        var xorData = issuedProtocol.getHeader()
                + issuedProtocol.getCabinetAddress()
                + issuedProtocol.getDataType()
                + issuedProtocol.getDataLen()
                + data;
        var xor = XorDataUtils.getXor(xorData.getBytes(StandardCharsets.UTF_8));
        issuedProtocol.setXor(xor.trim());

        var len = (loadData.length() / NumConstant.INT_TWO) + NumConstant.INT_TWO;
        // 负载有效数据长度
        issuedProtocol.setDataLen(HexUtil.fillZero(String.format("%04x", len), NumConstant.INT_FOUR).toUpperCase());
        // 消息唯一标识
        var flagXorData = issuedProtocol.getHeader()
                + issuedProtocol.getCabinetAddress()
                + issuedProtocol.getDataType()
                + issuedProtocol.getDataLen()
                + HexUtil.str2HexStr(System.currentTimeMillis() + "");
        var flagXor = XorDataUtils.getXor(flagXorData.getBytes(StandardCharsets.UTF_8));
        var messageId = HexUtil.fillZero(flagXor, NumConstant.INT_FOUR);
        issuedProtocol.setMessageUniqueIdentifier(messageId);

        log.info("中断业务指令重新下发指令messageId[{}],机柜[{}]", messageId, issuedProtocol.getCabinetAddress());
        return issuedProtocol.getAllData().replaceAll(" ", "");
    }

    private void deleteCacheAddJobAndCache(List<CommandIssuedSuccess> lists) {
        if (CollectionUtils.isEmpty(lists)) {
            return;
        }

        for (var command : lists) {
            // 更新之前先查询
            commandOperationService.interruptReIssuedUpdateCache(command);
        }
    }


    private CommandIssuedSuccess getSingleCommandIssuedSuccess(int cab, int boardNo, int cellNum, String door,
                                                               int expectedTimeSecond, int orderNo, long stepId, String setType) {
        var cs = new CommandIssuedSuccess();
        cs.setReqCabinetNo(String.valueOf(cab));
        cs.setSettings("{}");
        cs.setOrder(orderNo);
        cs.setStepId(stepId);
        cs.setSetType(setType);
        cs.setChannelNo(cellNum);
        cs.setDoor(door);
        cs.setBoardNo(boardNo);
        cs.setInterruptOrEndStepId(stepId);
        cs.setExpectedTimeSecond(Math.toIntExact(expectedTimeSecond));
        return cs;
    }

    private CommandIssuedSuccess getAllCommandIssuedSuccess(int cab, int expectedTimeSecond, int orderNo, long stepId, String setType) {
        var cs = new CommandIssuedSuccess();
        cs.setReqCabinetNo(String.valueOf(cab));
        cs.setDoor("");
        cs.setSettings("{}");
        cs.setBoardNo(255);
        cs.setChannelNo(255);
        cs.setOrder(orderNo);
        cs.setStepId(stepId);
        cs.setSetType(setType);
        cs.setInterruptOrEndStepId(stepId);
        cs.setExpectedTimeSecond(Math.toIntExact(expectedTimeSecond));
        return cs;
    }

    private void commandFirstData(CommandIssuedSuccess command) {
        var firstCommand = NumConstant.INT_ONE == command.getOrder();
        var commandOutInfos = commandBoardInfoService.getCommandOutInfo(command, firstCommand);
        var cab = Integer.parseInt(command.getReqCabinetNo());

        // 指令下发成功后,判断第一帧上送数据事件
        for (var coi : commandOutInfos) {
            commandFirstDataService.commandFirstData(new CommandFirstDataEvent(command, cab, coi.getBoardAddress(), coi.getCells()));
        }
    }

    private Pair<List<String>, List<CommandIssuedSuccess>> getPair(List<OperationDetail> ops, String cab) {
        List<String> cmdList = new ArrayList<>();
        List<CommandIssuedSuccess> commandIssuedSuccessList = new ArrayList<>();
        cab = String.valueOf(Integer.parseInt(cab));

        if (CollectionUtils.isEmpty(ops)) {
            return Pair.of(cmdList, commandIssuedSuccessList);
        }

        for (var op : ops) {
            var sd = settingDetailMapper.queryListByStepIdAndSettingOrder(Long.valueOf(op.getStepId()), op.getOrderNo());
            if (null == sd) {
                continue;
            }

            var boardConfig = boardConfigMapper.queryByChannelNo(op.getBoardAddress(), op.getCellNum());
            if (null == boardConfig || StringUtils.isBlank(boardConfig.getDoor())) {
                continue;
            }

            if (null == op.getInterruptTimeSecond() && null == sd.getExpectedTime()) {
                log.info("单个恒流板断连重新下发,未找到中断执行时间或工步方案:{}-{}", cab, gson.toJson(op));
                continue;
            }

            // 计算剩余时长 秒
            var nowTime = System.currentTimeMillis();
            var diff = (nowTime - op.getBeginTime()) / NumConstant.LONG_ONE_THOUSAND;
            var time = null != op.getInterruptTimeSecond() ? op.getInterruptTimeSecond() : diff;

            long expectedTimeSecond = getTime(time, sd.getExpectedTime());
            var cs = new CommandIssuedSuccess();
            cs.setSettings("{}");
            cs.setReqCabinetNo(cab);
            cs.setOrder(op.getOrderNo());
            cs.setStepId(sd.getStepId());
            cs.setSetType(sd.getSetType());
            cs.setChannelNo(op.getCellNum());
            cs.setDoor(boardConfig.getDoor());
            cs.setBoardNo(op.getBoardLineNum() - NumConstant.INT_ONE);
            cs.setInterruptOrEndStepId(Long.valueOf(op.getStepId()));
            cs.setExpectedTimeSecond(Math.toIntExact(expectedTimeSecond));
            var cmd = service.getInterruptReIssuedCmd(cs, (int) expectedTimeSecond);
            if (StringUtils.isNotBlank(cmd)) {
                cmdList.add(cmd);
                commandIssuedSuccessList.add(cs);
            }
            // 单通道
            if (ServerConstant.SINGLE_TYPE_SINGLE == op.getSingleType()) {
                CommandIssuedSuccess command = getSingleCommandIssuedSuccess(Integer.parseInt(cab),
                        op.getBoardLineNum() - NumConstant.INT_ONE, op.getCellNum(), boardConfig.getDoor(),
                        Math.toIntExact(expectedTimeSecond), op.getOrderNo(), sd.getStepId(), sd.getSetType());
                var cacheKey = CaffeineUtil.getCacheKey(Integer.parseInt(command.getReqCabinetNo()), command.getBoardNo(), command.getChannelNo(), command.getOrder());
                CaffeineUtil.set(CaffeineUtil.CACHE_KEY_COMMAND_FIRST + cacheKey, System.currentTimeMillis(), 50);
                commandFirstData(command);
            }

            // 全体通道
            if (ServerConstant.SINGLE_TYPE_ALL == op.getSingleType() && null != op.getInterruptTimeSecond()) {
                CommandIssuedSuccess command = getAllCommandIssuedSuccess(Integer.parseInt(cab),
                        Math.toIntExact(expectedTimeSecond), op.getOrderNo(), sd.getStepId(), sd.getSetType());
                var key = "commandAll&" + cab;
                CommandIssuedSuccess cache = CaffeineUtil.get(key);
                if (null != cache) {
                    int newTime = Math.max(command.getExpectedTimeSecond(), cache.getExpectedTimeSecond());
                    cache.setExpectedTimeSecond(newTime);
                    CaffeineUtil.set(key, cache, 10);
                } else {
                    CaffeineUtil.set(key, command, 10);
                }
            }

            // todo 更新缓存 中断后重新计算
            cacheUpdate(cs);
        }

        return Pair.of(cmdList, commandIssuedSuccessList);
    }

    private void cacheUpdate(CommandIssuedSuccess cs) {
        var cacheKey = Integer.parseInt(cs.getReqCabinetNo()) + CaffeineUtil.CACHE_KEY_SYMBOL + cs.getBoardNo() + CaffeineUtil.CACHE_KEY_SYMBOL + cs.getChannelNo();
        // 更新容量统计缓存
        var opKey = KeyUtil.commandOperateKey(Integer.parseInt(cs.getReqCabinetNo()), cs.getBoardNo(), cs.getChannelNo());
        OperationVo ovo = operationVoCacheService.getOperationVo(opKey);
        var stats = cachePutOrGet.getStatsCache(cacheKey);
        if (null != stats) {
            stats.setC(NumConstant.DOUBLE_ZERO);
            stats.setTs(NumConstant.DOUBLE_ZERO);
        }
        if (null != ovo) {
            ovo.setOperationStartTime(System.currentTimeMillis());
            operationVoCacheService.putOperationVo(opKey, ovo);
        }
        var cacheDlKey = Integer.parseInt(cs.getReqCabinetNo()) + CaffeineUtil.CACHE_KEY_SYMBOL + cs.getBoardNo() + CaffeineUtil.CACHE_KEY_SYMBOL + cs.getChannelNo();
        CaffeineUtil.delete(CaffeineUtil.FIRST_DATA_LIST_KEY + cacheKey);
        CaffeineUtil.delete(CaffeineUtil.FIRST_DATA_LIST_KEY + cacheDlKey);
    }
}



